/**   
* @Title: FrequencyHandlerInterceptor.java
* @Package org.xaoyaoyao.cloud.web.interceptor
* @Description: TODO
* @author xaoyaoyao
* @date Feb 11, 2019 2:34:05 PM
* @version V1.0   
*/
package org.xaoyaoyao.cloud.web.interceptor;

import java.io.IOException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Random;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.handler.HandlerInterceptorAdapter;
import org.springframework.web.servlet.resource.ResourceHttpRequestHandler;
import org.xaoyaoyao.cloud.web.limit.FrequencyStruct;
import org.xaoyaoyao.cloud.web.limit.RequestLimit;
import org.xaoyaoyao.cloud.common.result.RetCode;
import org.xaoyaoyao.cloud.common.utils.IPUtils;
import org.xaoyaoyao.cloud.web.rsp.RspUtils;

import com.alibaba.fastjson.JSON;

import lombok.extern.slf4j.Slf4j;

/**
 * @ClassName: FrequencyHandlerInterceptor
 * @Description: TODO
 * @author xaoyaoyao
 * @date Feb 11, 2019 2:34:05 PM
 * 
 */
@Slf4j
public class FrequencyHandlerInterceptor extends HandlerInterceptorAdapter {

	private static final int MAX_BASE_STATION_SIZE = 100000;

	private static Map<String, FrequencyStruct> BASE_STATION = new HashMap<String, FrequencyStruct>(MAX_BASE_STATION_SIZE);

	private static final float SCALE = 0.75F;

	private static final int MAX_CLEANUP_COUNT = 3;

	private static final int CLEANUP_INTERVAL = 1000;

	private Object syncRoot = new Object();

	private int cleanupCount = 0;

	@Override
	public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
		log.info(">>>>>>>> 请求: {} 开始处理", request.getRequestURI());
		boolean result = true;
		try {
			if (handler instanceof ResourceHttpRequestHandler) {
				return result;
			}
			RequestLimit methodFrequency = ((HandlerMethod) handler).getMethodAnnotation(RequestLimit.class);
			RequestLimit classFrequency = ((HandlerMethod) handler).getBean().getClass().getAnnotation(RequestLimit.class);
			if (classFrequency != null) {
				result = handleFrequency(request, response, classFrequency);
			}
			if (result && methodFrequency != null) {
				result = handleFrequency(request, response, methodFrequency);
			}
		} catch (Exception e) {
			log.error(e.getMessage(), e);
		}
		log.info(">>>>>>>> 请求result结果: {}", result);
		return result;
	}

	private boolean handleFrequency(HttpServletRequest request, HttpServletResponse response, RequestLimit limit) throws IOException {
		if (limit == null) {
			return true;
		}
		String name = limit.name();
		int lt = limit.limit();
		int time = limit.time();
		log.info(">>>>>>>> name >> {}, limit >> {}, time >> {}", name, lt, time);
		if (time == 0 || lt == 0) {
			response.setStatus(HttpServletResponse.SC_FORBIDDEN);
			issueDenied(request, response);
			return false;
		}
		long currentTimeMilles = System.currentTimeMillis() / 1000;
		String ip = IPUtils.getRemoteIP(request);
		String key = name.concat("_").concat(ip).concat(request.getRequestURI().toString());
		FrequencyStruct frequencyStruct = BASE_STATION.get(key);
		if (frequencyStruct == null) {
			frequencyStruct = new FrequencyStruct();
			frequencyStruct.uniqueKey = name;
			frequencyStruct.start = frequencyStruct.end = currentTimeMilles;
			frequencyStruct.limit = lt;
			frequencyStruct.time = time;
			frequencyStruct.accessPoints.add(currentTimeMilles);
			synchronized (syncRoot) {
				BASE_STATION.put(key, frequencyStruct);
			}
			if (BASE_STATION.size() > MAX_BASE_STATION_SIZE * SCALE) {
				cleanup(currentTimeMilles);
			}
		} else {
			frequencyStruct.end = currentTimeMilles;
			frequencyStruct.accessPoints.add(currentTimeMilles);
		}
		// 时间是否有效
		if (frequencyStruct.end - frequencyStruct.start >= time) {
			log.debug(">>>>>> frequency struct be out of date, struct will be reset., struct: " + frequencyStruct.toString());
			frequencyStruct.reset(currentTimeMilles);
		} else {
			int count = frequencyStruct.accessPoints.size();
			if (count > lt) {
				log.error(">>>>>>>> key: " + key + " too frequency. count: " + count + ", limit: " + limit + ".");
				response.setStatus(HttpServletResponse.SC_FORBIDDEN);
				issueDenied(request, response);
				return false;
			}
		}
		return true;
	}

	private void issueDenied(HttpServletRequest request, HttpServletResponse response) throws IOException {
		Map<String, Object> ret = RspUtils.makeRsp(RetCode.REQUEST_TOO_FREQUENTLY, "Request too frequently");
		response.setContentType("application/json; charset=UTF-8");
		String json = JSON.toJSONString(ret, false);
		response.getWriter().write(json);
		response.flushBuffer();
	}

	private void cleanup(long currentTimeMilles) {
		synchronized (syncRoot) {
			Iterator<String> it = BASE_STATION.keySet().iterator();
			while (it.hasNext()) {
				String key = it.next();
				FrequencyStruct struct = BASE_STATION.get(key);
				if ((currentTimeMilles - struct.end) > struct.time) {
					it.remove();
				}
			}
			if ((MAX_BASE_STATION_SIZE - BASE_STATION.size()) > CLEANUP_INTERVAL) {
				cleanupCount = 0;
			} else {
				cleanupCount++;
			}
			if (cleanupCount > MAX_CLEANUP_COUNT) {
				randomCleanup(MAX_CLEANUP_COUNT);
			}
		}
	}

	/**
	 * 随机淘汰count个key
	 * 
	 * @param maxCleanupCount
	 */
	private void randomCleanup(int count) {
		// 防止调用错误
		if (BASE_STATION.size() < MAX_BASE_STATION_SIZE * SCALE) {
			return;
		}
		Iterator<String> it = BASE_STATION.keySet().iterator();
		Random random = new Random();
		int tempCount = 0;
		while (it.hasNext()) {
			if (random.nextBoolean()) {
				it.remove();
				tempCount++;
				if (tempCount >= count) {
					break;
				}
			}
		}
	}
}